<?php
namespace app\command;

use app\common\model\coin\CoinRule;
use app\common\model\coin\CoinRuleRun;
use app\common\model\coin\CoinRuleRunTrade;
use mall\library\Huobi;
use think\console\Command;
use think\console\Input;
use think\console\Output;
use think\facade\Db;


/**
 * Socket中转处理
 * Class HistoryTrade
 * @package app\command
 */
class SocketTransfer extends Command
{

    /**
     * ENV 配置参数KEY列表
     */
    const ENV_KEY_LIST = [
        'SOCKET_PORT'   => 'history_socket.socket_query_port',
        'SOCKET_HOST'   => 'history_socket.socket_query_host',
    ];

    protected static $opcodes = [
        'continuation' => 0,
        'text'         => 1,
        'binary'       => 2,
        'close'        => 8,
        'ping'         => 9,
        'pong'         => 10,
    ];

    /**
     * 初始化的内存表
     */
    const TABLE_LIST = [
        'CONFIG'   => ['config',true,false,1,[      //【表名,是否初始化创建,是否启用锁,记录数（1024的倍数）,字段参数】
            ['value','TYPE_STRING',20],  // 配置的值
        ]], //配置信息
        'SOCKET_CLIENT_ID' => ['socket_client_id',true,false,1,[  //swoole中转的列表
            ['status','TYPE_INT',1],    // 状态：1=>需申请请求；2=>获取数据，待处理，3=>处理中，4=>处理成功，但未修改，5=>完成，6=>请求失败
        ]],  // API 请求参数设置
        'SOCKET_CLIENT_SEND' => ['socket_client_send',true,false,10,[  //swoole中转的列表
            ['client_id','TYPE_INT',11],  // 简介的id
            ['message','TYPE_STRING',20480], // 发送内容
            ['opcode','TYPE_INT',1],    //  发送类型
        ]],  // API 请求参数设置
        'AUTO_ID' => ['auto_id',true,true,1,[  //swoole中转的列表
            ['id','TYPE_INT',4],
        ]],  // API 请求参数设置


    ];

    /**
     * 重写configure
     * {@inheritdoc}
     */
    protected function configure()
    {
        // 命令的名字（"think" 后面的部分）
        $this->setName('HistoryTest')
            // 配置一个参数 使用$input->getArgument('username')获取
            // 运行 "php think list" 时的简短描述
            ->setDescription('实时获取货币数据，并进行保存处理。')
            // 运行命令时使用 "--help" 选项时的完整命令描述
            ->setHelp("一直执行 无参数");
    }


    /**
     *  * 重写execute
     *  * {@inheritdoc}
     *
     * @param Input $input
     * @param Output $output
     */
    protected function execute(Input $input, Output $output)
    {

    /*    echo floatval('0.04128070228258921700');exit;*/
        $trimFunc = function($value) {
            return rtrim(rtrim(bcadd($value,0,20),0),'.');
        };
        $ruleIdList = [];
        Db::startTrans();
        try {
            if($result = (new CoinRuleRun())->where('(quantity - sell_quantity) * unit_price > 5 AND sell_quantity > 0')->select()) {
                echo count($result),"\n";
                foreach($result as $runDetails) {
                    $runDetails = $runDetails->getData();
                    $ruleId = $runDetails['coin_rule_id'];
                    if(in_array($ruleId,$ruleIdList)) {
                        continue;
                    }

                    $ruleIdList[] = $ruleId;
                    $runList = [];
                    $tradeList = (new CoinRuleRunTrade())->alias('trade')
                        ->join('Coin coin','coin.id=trade.coin_id','inner')
                        ->join('Coin_rule_run run','run.id=trade.coin_rule_run_id','inner')
                        ->join('Users user','user.id=trade.user_id','inner')
                        ->join('Users_key key','key.id=trade.user_id','inner')
                        ->where([
                            ['trade.status','>',0],
                            ['trade.coin_rule_id','=',$ruleId],
                        ])
                        ->order('trade.create_time','ASC')
                        ->field([
                            'run.id',
                            'run.amount',
                            'run.quantity',
                            'run.unit_price',
                            'run.sell_amount',
                            'run.sell_quantity',
                            'run.sell_price',
                            'run.last_remaining_quantity',
                            'run.version',

                            'trade.id'=>'trade_id',
                            'trade.quantity'=>'trade_quantity',
                            'trade.unit_price'=>'trade_unit_price',
                            'trade.type',
                            'trade.status',

                            'user.username',

                            'key.account_id',
                            'key.access_key',
                            'key.secret_key',

                            'coin.code',
                        ])
                        ->select();
                    foreach($tradeList as $tradeDetails) {
                        $tradeDetails = $tradeDetails->getData();
                        $id = $tradeDetails['id'];
                        if(!array_key_exists($id,$runList)) {
                            $runList[$id] = [];
                        }
                        if($tradeDetails['type'] == 1) {
                            // 买入
                            if(!array_key_exists('first',$runList[$id])) {
                                $runList[$id]['first'] = $tradeDetails;
                            }
                     //            $runList[$id]['last_buy'] = $tradeDetails;
                        } elseif($tradeDetails['type'] == 2) {
                            // 卖出
                            $runList[$id]['sell'] = $tradeDetails;
                        }
                    }

                    $reserved = [
                        'quantity'  => 0,
                        'price'     => 0,
                        'amount'    => 0,
                    ];

                    foreach($runList as $runId=>&$details) {
                        if(bccomp($reserved['quantity'],0,20) == 1) {
                            (new CoinRuleRunTrade())->where([
                                'id'    => $details['first']['trade_id'],
                            ])->update([
                                'reserved_quantity' => $reserved['quantity'],
                                'reserved_price' => $reserved['price'],
                            ]);
                            if(array_key_exists('sell',$details)) {
                                // 存在
                                $details['sell']['amount'] = $trimFunc(bcadd($details['sell']['amount'],$reserved['amount'],20));
                                $details['sell']['quantity'] = $trimFunc(bcadd($details['sell']['quantity'],$reserved['quantity'],20));
                                $details['sell']['unit_price'] = $trimFunc(bcdiv($details['sell']['amount'],$details['sell']['quantity'],20));

                                // 修改持仓参数
                                (new CoinRuleRun())->where([
                                    'id'    => $details['sell']['id'],
                                    'version'    => $details['sell']['version'],
                                ])->update([
                                    'amount' =>  $details['sell']['amount'],
                                    'quantity' =>  $details['sell']['quantity'],
                                    'unit_price' =>  $details['sell']['unit_price'],
                                ]);

                                $reserved = [
                                    'quantity'  => 0,
                                    'price'     => 0,
                                    'amount'    => 0,
                                ];
                            } else {
                                $runFind = (new CoinRule())->alias('rule')
                                    ->join('Coin coin','coin.id=rule.coin_id','inner')
                                    ->join('Coin_rule_run run','run.id=rule.coin_rule_run_id','inner')
                                    ->where(['rule.id'=>$ruleId])->field(['run.id','run.amount','run.quantity','run.unit_price','run.last_remaining_quantity','coin.code'])->select();
                                $runFind = $runFind[0]->getData();

                                $priceList = [
                                    $runFind['unit_price'],
                                ];

                                // 存在
                                $runFind['amount'] = $trimFunc(bcadd($runFind['amount'],$reserved['amount'],20));
                                $runFind['quantity'] = $trimFunc(bcadd($runFind['quantity'],$reserved['quantity'],20));
                                $runFind['unit_price'] = $trimFunc(bcdiv($runFind['amount'],$runFind['quantity'],20));
                                $priceList[] = $runFind['unit_price'];
                                $balance = (new Huobi($details['first']['account_id'],$details['first']['access_key'],$details['first']['secret_key']))->get_balance($details['first']['account_id']);
                                foreach($balance['data']['list'] as $currencyDetails) {
                                    if(($currencyDetails['currency'] . 'usdt') == $runFind['code'] && $currencyDetails['type'] == 'trade') {
                                        $balance = $currencyDetails['balance'];
                                        break;
                                    }
                                }

                                if(bccomp($balance,bcadd($runFind['quantity'],$runFind['last_remaining_quantity'],20),20) == -1) {
                                    // 小于，需要做
                                    $runFind['deviations_quantity'] = $trimFunc(bcsub($balance,bcadd($runFind['quantity'],$runFind['last_remaining_quantity'],20),20));
                                }

                                // 获取实时价格
                                $price = (new Huobi('','',''))->get_market_trade($runFind['code'])['tick']['data'][0]['price'];
                                array_unshift($priceList,$price);
                                if(array_key_exists('deviations_quantity',$runFind)) {
                                    echo '【余额少于现有金额】';
                                }
                                echo "规则ID：" . $ruleId . " 用户：" . $details['first']['username'] . ' 币种:' . $runFind['code'];
                                echo " 现价：" . $priceList[0] . ' 原均价：' . $priceList[1] . '(' . $trimFunc(bcmul(bcdiv($priceList[0],$priceList[1],4) - 1,100,2)) . '%)'
                                    . ' 现均价：' . $priceList[2] . '(' . $trimFunc(bcmul(bcdiv($priceList[0],$priceList[2],4) - 1,100,2)) . '%)' . "\n";

                                unset($runFind['code'],$runFind['last_remaining_quantity']);
                                // 修改持仓参数
                                (new CoinRuleRun())->where([
                                    'id'    => $runFind['id'],
                                ])->update($runFind);
                                break;
                            }
                        }

                        if(array_key_exists('sell',$details)) {
                            // 已卖出处理
                            $reservedQuantity = bcsub(bcadd($details['sell']['quantity'],$details['sell']['last_remaining_quantity'],20),$details['sell']['trade_quantity'],20);
                            if($reservedQuantity > 0) {
                                $reserved = [
                                    'quantity'  => $trimFunc($reservedQuantity),
                                    'price'     => $trimFunc($details['sell']['trade_unit_price']),
                                    'amount'    => $trimFunc(bcmul($reservedQuantity,$details['sell']['trade_unit_price'],20)),
                                ];

                                // 修改执行中的保留字段
                                (new CoinRuleRun())->where([
                                    'id'    => $details['sell']['id']
                                ])->update([
                                    'reserved_quantity' => $reserved['quantity'],
                                    'reserved_price' => $reserved['price'],
                                ]);
                                (new CoinRuleRunTrade())->where([
                                    'id'    => $details['sell']['trade_id'],
                                ])->update([
                                    'reserved_quantity' => $reserved['quantity'],
                                    'reserved_price' => $reserved['price'],
                                ]);
                            }
                        }

                    }

                    $end = end($runList);
                    if(array_key_exists('sell',$end)) {
                        echo "【已关闭策略】规则ID：" . $ruleId . " 用户：" . $end['first']['username'] . ' 币种:' . $end['first']['code'] . ' 执行ID：' . $end['first']['id'] . "\n";
                    }
                }
            }


            $select = (new CoinRuleRunTrade())->alias('trade')
                ->join('Coin_rule_run run','run.id=trade.coin_rule_run_id','inner')
                ->where([
                    ['trade.type','=',2],
                    ['trade.status','>',1],
            //        ['trade.reserved_quantity','>',0],
                    Db::raw('trade.quantity + trade.reserved_quantity <> run.quantity'),
                ])->field([
                    'trade.id'    => 'trade_id',
                    'trade.quantity'    => 'trade_quantity',
                    'trade.reserved_quantity',
                    'run.quantity'  => 'quantity',
                    'run.last_remaining_quantity'  => 'last_remaining_quantity',
                ])->select();
            foreach($select as $one) {
                $one = $one->getData();
                $diff =  $trimFunc(bcsub(bcsub(bcadd($one['last_remaining_quantity'],$one['quantity'],20),$one['trade_quantity'],20),$one['reserved_quantity'],20));
                if($diff) {
                    echo $one['trade_id'] . ' : ' . $diff . ' ' . $one['quantity'] . ' ' . $one['trade_quantity'] . ' ' . $one['reserved_quantity'],"\n";
                }
            }
            echo '执行数据整理成功';
         //   Db::commit();
        }catch (\Exception $e){
            echo $e->getMessage();
            Db::rollback();
        }

        exit;

        // 创建内存表
        static::swooleTable();

        // 开启socket服务器
        static::startWebSocket();

        //开启等待
        Swoole::wait();
    }

    /**
     * 启动swoole Server
     */
    static function startWebSocket()
    {
        Swoole::startProcess(function(){
            //创建websocket服务器对象，监听0.0.0.0:9502端口

            $ws = new \swoole_websocket_server(self::socketHost(), self::socketPort());

            //监听WebSocket连接打开事件
            $ws->on('open', function ($ws, $request) {
                $fd = $request->fd;
                $clientTable = self::swooleTable('SOCKET_CLIENT_ID');
                $clientTable->set($fd,['status'=>1]);
                $client = new \WebSocket\Client($request->get['uri'],[
                    'filter'    => ['binary'],
                    'timeout'   => 1,
                ]);
                echo $request->get['uri'],"\n";
                // 链接指定地址
                \swoole_timer::tick(10,function(int $timer_id, $ws) use($clientTable,$fd,$client){
                    if(!$clientTable->exist($fd)) {
                        \swoole_timer::clear($timer_id);
                        return false;
                    }
                    $sendTable = self::swooleTable('SOCKET_CLIENT_SEND');
                    foreach($sendTable as $key=>$value) {
                        if($value['client_id'] != $fd) {
                            continue;
                        }
                        $sendTable->del($key);
                        var_dump($value['message']);
                        $client->send($value['message'],array_search($value['opcode'],self::$opcodes));
                    }
                    try {

                        $message = $client->receive();
                        var_dump($message);
                        $ws->push($fd,$message,WEBSOCKET_OPCODE_BINARY);
                    } catch (\Exception $e) {
                    }
                },$ws);

            });

            //监听WebSocket消息事件
            $ws->on('message', function ($ws, $frame) {
                $data = [
                    'client_id' => $frame->fd,
                    'message'   => $frame->data,
                    'opcode'    => $frame->opcode
                ];
                print_r($data);
                self::swooleTable('SOCKET_CLIENT_SEND')->set(self::getAutoId('SOCKET_CLIENT_SEND'),$data);
            });


            //监听WebSocket连接关闭事件
            $ws->on('close', function ($ws, $fd) {
                // 关闭链接
                self::swooleTable('SOCKET_CLIENT_ID')->del($fd);
            });

            $ws->start();
        });

    }


    /**
     * 获取tcp的监控端口
     */
    static function socketPort()
    {
        if(!$port = env(static::ENV_KEY_LIST['SOCKET_PORT'])) {
            $port = 9801;
        }
        return $port;
    }

    /**
     * 获取监听的ip
     */
    static function socketHost()
    {
        if(!$host = env(static::ENV_KEY_LIST['SOCKET_HOST'])) {
            $host = '0.0.0.0';
        }
        return $host;
    }


    /**
     * 创建及获取表数据
     * @param null $tableKey
     * @param string|null $tableSuffix
     * @return bool|mixed
     */
    private static function swooleTable($tableKey = null,string $tableSuffix = null) {
        if(is_null($tableKey)) {    // 初始化表
            foreach(static::TABLE_LIST as $table) {
                if(!$table[1]) {
                    continue;
                }
                // 创建Swoole表
                Swoole::table($table[0],$table[4],1024 * $table[3],$table[2]);
            }
            return true;
        }

        // 创建Swoole表
        $table = static::TABLE_LIST[$tableKey];
        if($tableSuffix) {
            $table[0] .= $tableSuffix;
        }
        return Swoole::table($table[0],$table[4],1024 * $table[3],$table[2]);
    }


    /**
     * 获取自增ID
     * @param string $tableName 使用到的表名
     * @return mixed 返回自增ID
     */
    static public function getAutoId(string $tableName = 'auto') {
        return static::swooleTable('AUTO_ID')->lockFunc(function($table) use($tableName){
            if(!$details = $table->get($tableName)) {
                $details = [
                    'id'    => 0,
                ];
            }
            $details['id']++;
            $table->set($tableName,$details);
            return $details['id'];
        });
    }
}